Writing a Drawing Engine
This section shows how to write a new drawing engine and add it to the QuickDraw 3D Acceleration Layer.
To develop a new drawing engine and add it to the QuickDraw 3D Acceleration Layer, you need to perform these seven steps:
- IMPORTANT
- You need to read this section only if you are developing custom 3D acceleration hardware or software. If you simply want to create a draw context and draw into it using low-level drawing functions, see "Using QuickDraw 3D RAVE," beginning on page 1-13.
![]()
The following sections describe some of these steps in more detail. The section "Supporting OpenGL Hardware," beginning on page 1-30 contains information that is useful if you are implementing a drawing engine to support hardware that is based on an OpenGL rasterization model.
- Write methods for the public functions pointed to by the fields of a draw context structure (for example,
setInt
). These methods are described in detail in the section "Public Draw Context Methods," beginning on page 1-115.- Write methods for the
TQADrawPrivateNew
andTQADrawPrivateDelete
function prototypes. These functions are called internally by theQADrawContextNew
andQADrawContextDelete
functions, respectively. You use these methods to allocate and release any private data (such as state variables) maintained by your drawing engine. These methods are described in detail in the section "Private Draw Context Methods," beginning on page 1-136.- Write methods for any texture and bitmap functions supported by your drawing engine (
TQATextureNew
,TQATextureDetach
,TQATextureDelete
,TQABitmapNew
,TQABitmapDetach
, andTQABitmapDelete
). These functions are called by their public counterparts (for example,QABitmapNew
). These methods are described in detail in the section "Texture and Bitmap Methods," beginning on page 1-141.- Write a method to handle the
QAEngineGestalt
function when your drawing engine is the target engine. This method is described in detail on (page 1-138).- Write a method to handle the
QAEngineCheckDevice
function when your drawing engine is the target engine. QuickDraw 3D RAVE calls this method to determine which devices your drawing engine supports. This method is described in detail on (page 1-138).- Write a method for the
TQAEngineGetMethod
function prototype. QuickDraw 3D RAVE calls this method to get some of your engine's methods during engine registration. This method is described in detail on (page 1-147).- Build your code as a shared library. The initialization routine of the shared library should register your drawing engine with QuickDraw 3D RAVE by calling the
QARegisterEngine
function.
Writing Public Draw Context Methods
As you've seen, the draw context structure (of typeTQADrawContext
) contains function pointers to the public draw context methods supported by your drawing engine. These methods are called whenever an application calls one of the public functions provided by QuickDraw 3D RAVE. For example, when an application calls theQADrawPoint
function for a draw context associated with your drawing engine, your engine'sTQADrawPoint
method (pointed to by thedrawPoint
field) is called. TheTQADrawPoint
method is declared like this:
typedef void (*TQADrawPoint) ( const TQADrawContext *drawContext, const TQAVGouraud *v);A draw context structure is passed as the first parameter to all the public draw context methods you need to define. This allows your methods to find the private data associated with the draw context (which is pointed to by thedrawPrivate
field).Notice that the function prototype for a point-drawing method passes the draw context as a
const
parameter. This indicates that your method should not alter any of the fields of the draw context structure passed to it. Only three draw context methods (namelyTQASetInt
,TQASetFloat
, andTQASetPtr
) are allowed to alter the draw context.Listing 1-7 shows a sample definition for a point-drawing method.
Listing 1-7 A
TQADrawPoint
method
void MyDrawPoint (const TQADrawContext *drawContext, const TQAVGouraud *v) { MyPrivateData *myData; /*our actual private data type*/ /*Cast generic drawPrivate pointer to our actual private data type.*/ myData = (MyPrivateData *) drawContext->drawPrivate; /*Call our z-buffered pixel drawing function with xyz and argb, and also pass it the current zfunction, which is stored in the private draw context data structure. Note that this isn't a complete implementation! (We should be using kQATag_Width, for example.)*/ MyDrawPixelWithZ(v->x, v->y, v->z, v->a, v->r, v->g, v->b, myData->stateVariable[kQATag_ZFunction]); }Once you've defined the necessary public draw context methods, you need to insert pointers to those methods into a draw context structure. You accomplish this step in your
- Note
- See "Public Draw Context Methods," beginning on page 1-115 for complete information on the public draw context methods your drawing engine must define.
![]()
TQADrawPrivateNew
method, described in the next section.Writing Private Draw Context Methods
Once you've written the public draw context methods supported by your drawing engine, you need to write several private draw context methods. In particular, you need to write aTQADrawPrivateNew
method to initialize a draw context and aTQADrawPrivateDelete
method to delete a draw context. TheTQADrawPrivateNew
method is called whenever an application creates a new draw context by calling theQADrawContextNew
function. Listing 1-8 illustrates a sampleTQADrawPrivateNew
method.Listing 1-8 A
TQADrawPrivateNew
method
TQAError MyDrawPrivateNew ( TQADrawContext *drawContext, const TQADevice*device, const TQARect *rect, const TQAClip *clip, unsigned long flags) { MyPrivateData *myData; /*Allocate a new MyPrivateData structure and store it in draw context.*/ myData = MyDataNew(...); drawContext->drawPrivate = (TQADrawPrivate *) myData; if (!myData) return (kQAOutOfMemory); /*Set the method pointers of drawContext to point to our draw methods.*/ newDrawContext->setFloat = MySetFloat; newDrawContext->setInt = MySetInt; ... return(kQANoErr); }As you can see, theMyDrawPrivateNew
function defined in Listing 1-8 allocates space for its private data, installs a pointer to that data in thedrawPrivate
field of the draw context structure, and then installs pointers to all the public draw context methods supported by the drawing engine into the draw context structure.Your
TQADrawPrivateDelete
method should simply undo any work done by yourTQADrawPrivateNew
method. In this case, the delete method just needs to release the private storage allocated by theTQADrawPrivateNew
method. Listing 1-9 shows a sampleTQADrawPrivateDelete
method.Listing 1-9 A
TQADrawPrivateDelete
method
void MyDrawPrivateDelete (TQADrawPrivate *drawPrivate) { MyDataDelete((MyPrivateData *) drawPrivate); }You register your private draw context methods with QuickDraw 3D RAVE using another private method, theTQAEngineGetMethod
method. See "Registering a Drawing Engine," beginning on page 1-29 for details.Handling Gestalt Selectors
To support calls to the public functionQAEngineGestalt
, your drawing engine must define aTQAEngineGestalt
method. This method returns information about the capabilities of your drawing engine. For example, suppose that your drawing engine supports texture mapping and accelerates both Gouraud shading and line drawing. Suppose further that you have been assigned a vendor ID of 5, and that the engine ID of your engine is 1001. In that case, you could define a method like the one shown in Listing 1-10.Listing 1-10 A
TQAEngineGestalt
method
TQAError MyEngineGestalt (TQAGestaltSelector selector, void *response) { const static char *myEngineName = "SurfDraw 3D"; switch (selector) { case kQAGestalt_OptionalFeatures: *((unsigned long *) response) = kQAOptional_Texture; break; case kQAGestalt_FastFeatures: *((unsigned long *) response) = kQAFast_Line | kQAFast_Gouraud; break; case kQAGestalt_VendorID: *((long *) response) = 5; break; case kQAGestalt_EngineID: *((long *) response) = 1001; break; case kQAGestalt_Revision: *((long *) response) = 0; break; case kQAGestalt_ASCIINameLength: *((long *) response) = strlen(myEngineName); break; case kQAGestalt_ASCIIName: strcpy(response, myEngineName); break; default: /*must flag unrecognized selectors*/ return (kQAParamErr); } return (kQANoErr); }If two different drawing engines should return identical vendor and engine IDs, QuickDraw 3D RAVE chooses the one that returns the most recent revision number (that is, the value returned for thekQAGestalt_Revision
selector). The larger number is considered newer.You register your
TQAEngineGestalt
method with QuickDraw 3D RAVE using theTQAEngineGetMethod
method, described in the next section.Registering a Drawing Engine
Once you written all the necessary public and private draw context methods, as well as methods to handle textures and bitmaps, you must write aTQAEngineGetMethod
method that reports the addresses of some of those methods to QuickDraw 3D RAVE. Listing 1-11 shows a sampleTQAEngineGetMethod
method. Notice that this method returns the addresses only of the private draw context methods and the methods to handle textures and bitmaps. The pointers for the public draw context methods are assigned directly to the fields of a draw context structure by yourTQADrawPrivateNew
method (as shown in Listing 1-8).Listing 1-11 A
TQAEngineGetMethod
method
TQAError MyEngineGetMethod (TQAEngineMethodTag methodTag, TQAEngineMethod *method) { switch (methodTag) { case kQADrawPrivateNew: method->drawPrivateNew = MyDrawPrivateNew; break; case kQADrawPrivateDelete: method->drawPrivateDelete = MyDrawPrivateDelete; break; case kQAEngineCheckDevice: method->engineCheckDevice = MyEngineCheckDevice; break; case kQAEngineGestalt: method->engineGestalt = MyEngineGestalt; break; case kQABitmapNew: method->bitmapNew = MyBitmapNew; break; case kQABitmapDetach: method->bitmapDetach = MyBitmapDetach; break; case kQABitmapDelete: method->bitmapDelete = MyBitmapDelete; break; default: return(kQANotSupported); } return(kQANoErr); }Finally, you register your drawing engine by passing the address of yourTQAEngineGetMethod
method to theQARegisterEngine
function:
QARegisterEngine(&MyEngineGetMethod);You can callQARegisterEngine
in two ways. During product development, you can link your drawing engine code directly with a test application, in which case you should callQARegisterEngine
from your application's initialization code. Alternatively, once you've completed development, you should build your engine's code into a shared library of type'tnsl'
. In this case, you should callQARegisterEngine
from the initialization routine of the shared library. When the shared library containing QuickDraw 3D RAVE is loaded, it searches for and loads any drawing engines contained in shared libraries in the current folder or in the Extensions folder.Supporting OpenGL Hardware
This section contains information that is useful if you are implementing a drawing engine to support hardware that is based on an OpenGL rasterization model. It describes special considerations for handling transparency and texture mapping.Transparency
QuickDraw 3D RAVE supports three transparency models: the premultiplied, interpolated, and OpenGL transparency models. Support for the OpenGL transparency model (indicated by thekQABlend_OpenGL
constant) should be automatic for hardware that is based on the OpenGL rasterization model. The other two models, indicated by thekQABlend_PreMultiply
andkQABlend_Interpolate
constants) may require emulation by your drawing engine.For example, consider the premultiplied blending function, specified by these equations:
(Here, the factors as, rs, gs, and bs represent the alpha, red, green and blue components of a source pixel; the factors ad, rd, gd, and bd represent the alpha, red, green and blue components of a destination pixel.)
OpenGL directly supports the premultiplied transparency blending function (and the interpolated transparency blending function) for the RGB components only. In other words, the alpha channel component (which is the same for both blending operations) cannot be directly implemented in OpenGL-compliant hardware. It is possible, however, to emulate these two transparency modes on OpenGL hardware, using several different methods. You can blend the RGB values only, or you can blend the ARGB values using a multipass algorithm. Which of these emulations you use depends on whether your drawing engine is associated with a frame buffer that stores an alpha channel or not.
- Note
- A complete description of how transparent objects are blended together with each of these models is provided in "Blending Operations" (page 1-49).
![]()
If your drawing engine is associated with a frame buffer that doesn't store an alpha channel value, you can implement the premultiplied and interpolated blending functions by simply ignoring the alpha channel component. These functions are then equivalent to OpenGL blending modes. The premulitplied blending function, with its alpha channel ignored, can be emulated by this function:
gBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);Similarly, the interpolated blending function, with its alpha channel ignored, can be emulated by this function:
gBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);To achieve a more complete blending, you can have your drawing engine rasterize each transparent object more than once, altering in each pass the blending mode, object alpha channel, and buffer write masks. The first pass should perform RGB blending. Accordingly, you should disable writing any alpha channel or z buffer data during this pass.
- IMPORTANT
- A drawing engine that uses this method of emulating the QuickDraw 3D RAVE blending functions on OpenGL hardware should not set the
kQAOptional_BlendAlpha
flag of thekQAGestalt_OptionalFeatures
selector to theQAEngineGestalt
function.![]()
/*first pass*/ glColorMask(TRUE, TRUE, TRUE, FALSE);/*disable alpha channel*/ glDepthMask(FALSE); /*disable Z buffer*/ if (premultpliedTransparency) glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); else glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); /*render the object here*/On the second pass, you should set the frame buffer alpha channel value to (1-as)(1-ad). To do this, you need to render the object again, with a different alpha value, as follows:
/*second pass*/ glColorMask(FALSE, FALSE, FALSE, TRUE);/*enable alpha channel*/ glDepthMask(FALSE); /*disable Z buffer*/ glBlendFunc(GL_ONE_MINUS_DST_ALPHA, GL_ZERO); /*render the object with alpha replaced with 1-a*/Finally, the third pass should replace the value in the alpha channel with the final value 1-((1-as)(1-ad)). To do this, you need to render the object again, with its alpha value set to 1, as follows:
/*third pass*/ glColorMask(FALSE, FALSE, FALSE, TRUE);/*enable alpha channel*/ glDepthMask(TRUE); /*enable Z buffer*/ glBlendFunc(GL_ONE_MINUS_DST_ALPHA, GL_ZERO); /*render the object with alpha replaced with 1*/After the third pass, the frame buffer contains the correctly blended object.Texture Mapping
QuickDraw 3D RAVE supports several texture mapping operations, which are controlled by the flags in thekQATag_TextureOp
state variable. Currently these flags are defined:
#define kQATextureOp_Modulate (1 << 0) #define kQATextureOp_Highlight (1 << 1) #define kQATextureOp_Decal (1 << 2) #define kQATextureOp_Shrink (1 << 3)To support the
- Note
- A complete description of texture mapping operations is provided in "Texture Operations" (page 1-51).
![]()
kQATextureOp_Modulate
mode on an OpenGL-compliant rasterizer, you can use theGL_MODULATE
mode, where thekd_r
,kd_g
, andkd_b
fields of a texture vertex specify the modulating color. Note, however, thatGL_MODULATE
does not allow these color values to be greater than 1.0, whereas QuickDraw 3D RAVE does allow them to be greater than 1.0. Values greater than 1.0 can provide improved image realism, and new hardware should support them. A more reasonable maximum modulation amplitude is 2.0.You can support the
kQATextureOp_Highlight
mode by performing two rendering passes. The first pass should render the texture-mapped object (possibly also with modulation, as just described), and the second pass should add the specular highlight value.
/*first pass*/ glDepthMask(FALSE); /*disable Z buffer*/ /*render the texture-mapped object here*/ /*second pass*/ glDepthMask(TRUE); /*enable Z buffer*/ glBlendFunc(GL_ONE, GL_ONE); /*add highlight color*/ /*render the highlight color as a Gouraud-shaded object here*/On the second pass, you should render the highlight color, using theks_r
,ks_g
, andks_b
fields of a texture vertex, as a Gouraud-shaded object.If the
kQATextureOp_Modulate
flag is clear (that is, is no texture map color modulation is to be performed), you can support thekQATextureOp_Decal
mode using the OpenGLGL_DECAL
mode. If, in addition, thekQATextureOp_Highlight
flag is set, you need to perform two rendering passes, as just described.
- IMPORTANT
- There is currently no known method of accurately rendering to OpenGL-compliant hardware when both the
kQATextureOp_Decal
and thekQATextureOp_Modulate
flags are set. You should determine the best method of implementing this mode correctly on your hardware. If your hardware cannot handle both modes at once, you should ignore thekQATextureOp_Modulate
mode wheneverkQATextureOp_Decal
is set.![]()